#include <stdio.h>
#include <string.h>
#include "run2.h"

int error;

int run2(BODY *root, SymbolTable *symboltable){
	run2DECL_LIST(root->decl_list, symboltable);
	run2STATEMENT_LIST(root->statement_list, symboltable);
	return run2Error;
}
void run2DECL_LIST(DECL_LIST *decl_list, SymbolTable *symboltable) {
	if (decl_list != 0) {
		run2DECL_LIST(decl_list->decl_list, symboltable);
		run2DECLARATION(decl_list->declaration, symboltable);
	}
}
void run2DECLARATION(DECLARATION *declaration, SymbolTable *symboltable) {
	switch (declaration->kind) {
	case typeidK:
		run2TYPE_ID(declaration, symboltable);
		break;
	case funcK:
		run2FUNC(declaration->val.function, symboltable);
		break;
	case varK:
		run2CLEARVAR_DECL_LIST(declaration->val.var_decl_list, initSymbolTable(), symboltable, "false");
		break;
	}
}
void run2FUNC(FUNCTION *function, SymbolTable *symboltable){
	SYMBOL *symbol;
	SYMBOL *symbol2;
	TYPE *type;
	symbol = getSymbol(symboltable, function->head->id);
	if(symbol==NULL){
		run2Error++;
		fprintf(stderr,"Error:%d: '%s' undeclared\n",function->lineno,function->head->id);
	}else{
		type = function->head->type;
		if(type->kind == type_idK){
			symbol2 = getSymbol(symboltable, type->val.id);
			if(symbol2==NULL){
				run2Error++;
				fprintf(stderr,"Error:%d: '%s' undeclared\n", function->lineno, type->val.id);
			}else{
				if(symbol2->kind!=typeSK){
					run2Error++;
					fprintf(stderr,"Error:%d: '%s' is not a type\n", function->lineno, type->val.id);
				}
			}
		}
		run2CLEARTYPE(type, initSymbolTable(),symboltable, "false");
		HEAD *head;
		head = function->head;
		if(head->par_decl_list!=0){
			run2CLEARVAR_DECL_LIST(head->par_decl_list->var_decl_list, initSymbolTable(),symboltable,"false");
		}
		run2DECL_LIST(function->body->decl_list, function->table);
		run2STATEMENT_LIST(function->body->statement_list, function->table);
	}
}
void run2TYPE_ID(DECLARATION *declaration, SymbolTable *symboltable){
	SymbolTable *tmpTable;
	tmpTable = initSymbolTable();
	putSymbolType(tmpTable, declaration->val.DECL_TYPE.id, NULL);
	error = 0;
	run2CLEARTYPE(declaration->val.DECL_TYPE.type, tmpTable, symboltable, "false");
	if(error>0){
		run2Error++;
		fprintf(stderr,"Error:%d: '%s' ends in a type-loop\n", declaration->lineno, declaration->val.DECL_TYPE.id);
		error = 0;
	}
}
void run2CLEARTYPE(TYPE *type, SymbolTable *tmpTable, SymbolTable *symboltable, char *bool){
	// this function only checks for loops in type declarations, and if type_id's exists
	SYMBOL *symbol;
	if(type->kind == type_idK){
		if(getSymbol(tmpTable, type->val.id)==NULL){
			putSymbolType(tmpTable, type->val.id, NULL);
			symbol = getSymbol(symboltable, type->val.id);
			if(symbol!=NULL){
				if(symbol->kind == typeSK){
					run2CLEARTYPE(symbol->val.type, tmpTable,symboltable,bool);
				}else{
					run2Error++;
					fprintf(stderr,"Error:%d: '%s' is not a type\n", type->lineno, type->val.id);
				}
			}else{
				run2Error++;
				fprintf(stderr,"Error:%d: use of non-existing type\n", type->lineno);
			}
		}else{
			if(strcmp(bool,"false")==0){
				error++;
			}
		}
	}
	if(type->kind == arrayK){
		run2CLEARTYPE(type->val.type, tmpTable, symboltable, "true");
	}
	if(type->kind == recordK){
		run2CLEARVAR_DECL_LIST(type->val.recordE.var_decl_list, tmpTable, symboltable, "true");
	}
}

void run2CLEARVAR_DECL_LIST(VAR_DECL_LIST *var_decl_list, SymbolTable *tmpTable, SymbolTable *symboltable, char *bool){
	switch(var_decl_list->kind){
	case var_decl_listK:
		run2CLEARVAR_DECL_LIST(var_decl_list->var_decl_list, tmpTable, symboltable, bool);
		run2CLEARVAR_TYPE(var_decl_list->var_type, tmpTable, symboltable, bool);
		break;
	case var_decl_list_baseK:
		run2CLEARVAR_TYPE(var_decl_list->var_type, tmpTable, symboltable, bool);
		break;
	}
}
void run2CLEARVAR_TYPE(VAR_TYPE *var_type, SymbolTable *tmpTable, SymbolTable *symboltable, char *bool){
	run2CLEARTYPE(var_type->type,tmpTable, symboltable,bool);
}

/*
 * STATEMENT-LIST FROM NOW ON!
 */

void run2STATEMENT_LIST(STATEMENT_LIST *statement_list,
		SymbolTable *symboltable) {
	switch (statement_list->kind) {
	case statement_list_baseK:
		run2STATEMENT(statement_list->statement, symboltable);
		break;
	case statement_listK:
		run2STATEMENT_LIST(statement_list->statement_list, symboltable);
		if(statement_list->statement!=NULL){
			run2STATEMENT(statement_list->statement, symboltable);
		}
		break;
	}
}
void run2STATEMENT(STATEMENT *statement, SymbolTable *symboltable){
	switch(statement->kind){
	case returnK:
		run2EXPRESSION(statement->val.return_expression, symboltable);
		break;
	case writeK:
		run2EXPRESSION(statement->val.write_expression, symboltable);
		break;
	case allocateK:
		run2VARIABLE(statement->val.allocateE.variable, symboltable);
		if(statement->val.allocateE.opt_length!=0){
			OPT_LENGTH *opt_length;
			opt_length = statement->val.allocateE.opt_length;
			run2EXPRESSION(opt_length->expression, symboltable);
		}
		break;
	case variableK:
		run2VARIABLE(statement->val.variableE.variable, symboltable);
		run2EXPRESSION(statement->val.variableE.expression, symboltable);
		break;
	case ifK:
		run2EXPRESSION(statement->val.ifE.expression, symboltable);
		run2STATEMENT(statement->val.ifE.statement, symboltable);
		if(statement->val.ifE.opt_else!=0){
			OPT_ELSE *opt_else;
			opt_else = statement->val.ifE.opt_else;
			run2STATEMENT(opt_else->statement, symboltable);
		}
		break;
	case whileK:
		run2EXPRESSION(statement->val.whileE.expression, symboltable);
		run2STATEMENT(statement->val.whileE.statement, symboltable);
		break;
	case statementlistK:
		run2STATEMENT_LIST(statement->val.statement_list, symboltable);
		break;
	}
}
TYPE *run2EXPRESSION(EXPRESSION *expression, SymbolTable *symboltable){
	switch(expression->kind){
	case timesK:
		run2EXPRESSION(expression->val.EXP_times.left, symboltable);
		run2EXPRESSION(expression->val.EXP_times.right, symboltable);
		return expression->type = makeTYPE_intconst();
		break;
	case divK:
		run2EXPRESSION(expression->val.EXP_div.left, symboltable);
		run2EXPRESSION(expression->val.EXP_div.right, symboltable);
		return expression->type = makeTYPE_intconst();
		break;
	case plusK:
		run2EXPRESSION(expression->val.EXP_plus.left, symboltable);
		run2EXPRESSION(expression->val.EXP_plus.right, symboltable);
		return expression->type = makeTYPE_intconst();
		break;
	case minusK:
		run2EXPRESSION(expression->val.EXP_minus.left, symboltable);
		run2EXPRESSION(expression->val.EXP_minus.right, symboltable);
		return expression->type = makeTYPE_intconst();
		break;
	case orK:
		run2EXPRESSION(expression->val.EXP_or.left, symboltable);
		run2EXPRESSION(expression->val.EXP_or.right, symboltable);
		return expression->type = makeTYPE_bool();
		break;
	case andK:
		run2EXPRESSION(expression->val.EXP_and.left, symboltable);
		run2EXPRESSION(expression->val.EXP_and.right, symboltable);
		return expression->type = makeTYPE_bool();
		break;
	case leqK:
		run2EXPRESSION(expression->val.EXP_leq.left, symboltable);
		run2EXPRESSION(expression->val.EXP_leq.right, symboltable);
		return expression->type = makeTYPE_bool();
		break;
	case geqK:
		run2EXPRESSION(expression->val.EXP_geq.left, symboltable);
		run2EXPRESSION(expression->val.EXP_geq.right, symboltable);
		return expression->type = makeTYPE_bool();
		break;
	case leK:
		run2EXPRESSION(expression->val.EXP_lessthan.left, symboltable);
		run2EXPRESSION(expression->val.EXP_lessthan.right, symboltable);
		return expression->type = makeTYPE_bool();
		break;
	case grK:
		run2EXPRESSION(expression->val.EXP_greaterthan.left, symboltable);
		run2EXPRESSION(expression->val.EXP_greaterthan.right, symboltable);
		return expression->type = makeTYPE_bool();
		break;
	case eqK:
		run2EXPRESSION(expression->val.EXP_eq.left, symboltable);
		run2EXPRESSION(expression->val.EXP_eq.right, symboltable);
		return expression->type = makeTYPE_bool();
		break;
	case neqK:
		run2EXPRESSION(expression->val.EXPneq.left, symboltable);
		run2EXPRESSION(expression->val.EXPneq.right, symboltable);
		return expression->type = makeTYPE_bool();
		break;
	case termK:
		return expression->type = run2TERM(expression->val.term, symboltable);
		break;
	}
	return NULL;
}
TYPE *run2TERM(TERM *term, SymbolTable *symboltable){
	SYMBOL *symbol;
	HEAD *head;
	ACT_LIST *act_list;
	switch(term->kind){
	case term_varK:
		return term->type = run2VARIABLE(term->val.variable, symboltable);
		break;
	case term_idK:
		symbol = getSymbol(symboltable, term->val.term_idE.id);
		if(symbol!=NULL){
			if(symbol->kind==functionSK){
				act_list = term->val.term_idE.act_list;
				if(act_list!=0){
					run2EXP_LIST(act_list->exp_list, symboltable);
				}
				head = symbol->val.function->head;
				return term->type = head->type;
			}else{
				run2Error++;
				fprintf(stderr,"Error:%d: function-call to non-existing function '%s'\n", term->lineno, term->val.term_idE.id);
			}
		}else{
			run2Error++;
			fprintf(stderr,"Error:%d: function-call to non-existing function '%s'\n", term->lineno, term->val.term_idE.id);
		}
		break;
	case term_expK:
		return term->type = run2EXPRESSION(term->val.expression, symboltable);
		break;
	case term_notK:
		return term->type = run2TERM(term->val.term, symboltable);
		break;
	case term_act_expK:
		run2EXPRESSION(term->val.act_expression, symboltable);
		return term->type = makeTYPE_intconst();
		break;
	case term_numK:
		return term->type = makeTYPE_intconst();
		break;
	case term_trueK:
		return term->type = makeTYPE_bool();
		break;
	case term_falseK:
		return term->type = makeTYPE_bool();
		break;
	case term_nullK:
		return term->type = NULL;
		break;
	}
	return NULL;
}
void run2EXP_LIST(EXP_LIST *exp_list, SymbolTable *symboltable){
	switch(exp_list->kind){
	case exp_list_baseK:
		run2EXPRESSION(exp_list->expression, symboltable);
		break;
	case exp_listK:
		run2EXP_LIST(exp_list->exp_list, symboltable);
		run2EXPRESSION(exp_list->expression, symboltable);
		break;
	}
}
TYPE *run2VARIABLE(VARIABLE *variable, SymbolTable *symboltable){
	SYMBOL *symbol;
	VAR_TYPE *var_type;
	TYPE *type;
	switch(variable->kind){
	case var_idK:
		symbol = getSymbol(symboltable, variable->val.id);
		if(symbol!=NULL){
			if(symbol->kind==variableSK){
				var_type = symbol->val.var_type;
				return variable->type = var_type->type;
			}else{
				run2Error++;
				fprintf(stderr, "Error:%d: '%s' is not a variable\n",variable->lineno, variable->val.id);
			}
		}else{
			run2Error++;
			fprintf(stderr, "Error:%d: use of non-existing variable '%s'\n",variable->lineno, variable->val.id);
		}
		break;
	case var_expK:
		type = run2VARIABLE(variable->val.var_expE.variable,symboltable);
		run2EXPRESSION(variable->val.var_expE.expression, symboltable);
		if(type!=NULL){
			while(type->kind == type_idK){
				symbol = getSymbol(symboltable,type->val.id);
				if(symbol->kind!=typeSK){
					run2Error++;
					fprintf(stderr, "Error:%d: use of non-existing variable '%s'\n",variable->lineno, variable->val.var_dotE.id);
					break;
				}else{
					type = symbol->val.type;
				}
			}
			if(type->kind == arrayK){
				return variable->type = type->val.type;
			}
		}
		break;
	case var_dotK:
		type = run2VARIABLE(variable->val.var_dotE.variable, symboltable);
		if(type!=NULL){
			while(type->kind == type_idK){
				symbol = getSymbol(symboltable,type->val.id);
				if(symbol->kind!=typeSK){
					run2Error++;
					fprintf(stderr, "Error:%d: use of non-existing variable '%s'\n",variable->lineno, variable->val.var_dotE.id);
					break;
				}else{
					type = symbol->val.type;
				}
			}
			if(type->kind == recordK){
				symbol = getSymbol(type->val.recordE.table, variable->val.var_dotE.id);
				if(symbol!=NULL){
					if(symbol->kind == variableSK){
						var_type = symbol->val.var_type;
						return variable->type = var_type->type;
					}else{
						run2Error++;
						fprintf(stderr, "Error:%d: use of non-existing variable '%s'\n",variable->lineno, variable->val.var_dotE.id);
					}
				}else{
					run2Error++;
					fprintf(stderr, "Error:%d: use of non-existing variable '%s'\n",variable->lineno, variable->val.var_dotE.id);
				}
			}
		}
		break;
	}
	return NULL;
}
